//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;


namespace EffiProzDB
{


    /**
     * A Grantee Object holds the name, access and administrative rights for a
     * particular grantee.<p>
     * It supplies the methods used to grant, revoke, test
     * and check a grantee's access rights to other database objects.
     * It also holds a reference to the common PUBLIC User Object,
     * which represent the special user refered to in
     * GRANT ... TO PUBLIC statements.<p>
     * The check(), isAccessible() and getGrantedClassNames() methods check the
     * rights granted to the PUBLIC User Object, _in addition to individually
     * granted rights, _in order to decide which rights exist for the user.
     *
     * Method names ending _in Direct indicate methods which do not recurse
     * to look through Roles which "this" object is a member of.
     *
     * We use the word "Admin" (e.g., _in private variable "admin" and method
     * "isAdmin()) to mean this Grantee has admin priv by any means.
     * We use the word "adminDirect" (e.g., _in private variable "adminDirect"
     * and method "isAdminDirect()) to mean this Grantee has admin priv
     * directly.
     *
     * @author boucherb@users
     * @author fredt@usrs
     * @author unsaved@users
     *
     * @version 1.8.0
     * @since 1.8.0
     */
    public class Grantee
    {

        public bool isRole;

        /**
         * true if this grantee has database administrator priv directly
         *  (ie., not by membership _in any role)
         */
        private bool _isAdminDirect = false;

        /** true if this grantee has database administrator priv by any means. */
        private bool _isAdmin = false;

        /** contains righs granted direct, or via roles, expept those of PUBLIC */
        private Dictionary<object, int> fullRightsMap = new Dictionary<object, int>();

        /**
         * Grantee name.
         */
        private string granteeName;

        /** map with database object identifier keys and access privileges values */
        private Dictionary<object, int> rightsMap;

        /** These are the DIRECT roles.  Each of these may contain nested roles */
        public HashSet<string> roles = new HashSet<string>();

        /**
         * The special PUBLIC Grantee object. <p>
         *
         * Note: All Grantee objects except the special
         * SYS and PUBLIC Grantee objects contain a reference to this object
         */
        private Grantee pubGrantee;

        /** Needed only to give access to the roles for this database */
        private GranteeManager granteeManager;

        /**
         * Constructor, with a argument reference to the PUBLIC User Object which
         * is null if this is the SYS or PUBLIC user.
         *
         * The dependency upon a GranteeManager is undesirable.  Hopefully we
         * can get rid of this dependency with an IOC or Listener re-design.
         */
        public Grantee(string name, Grantee inGrantee,
                GranteeManager man)
        {

            rightsMap = new Dictionary<object, int>();
            granteeName = name;
            granteeManager = man;
            pubGrantee = inGrantee;
        }

        public string getName()
        {
            return granteeName;
        }

        /**
         * Retrieves the map object that represents the rights that have been
         * granted on database objects.  <p>
         *
         * The map has keys and values with the following interpretation: <P>
         *
         * <UL>
         * <LI> The keys are generally (but not limited to) objects having
         *      an attribute or value equal to the name of an actual database
         *      object.
         *
         * <LI> Specifically, the keys act as database object identifiers.
         *
         * <LI> The values are always int objects, each formed by combining
         *      a set of flags, one for each of the access rights defined in
         *      UserManager: {SELECT, INSERT, UPDATE and DELETE}.
         * </UL>
         */
        public Dictionary<object, int> getRights()
        {

            // necessary to create the script
            return rightsMap;
        }

        /**
         * Grant a role
         */
        public void grant(string role)
        {
            roles.Add(role);
        }

        /**
         * Revoke a direct role only
         */
        public void revoke(string role)
        {

            if (!hasRoleDirect(role))
            {
                throw Trace.error(Trace.DONT_HAVE_ROLE, role);
            }

            roles.Remove(role);
        }

        /**
         * Gets direct roles, not roles nested within them.
         */
        public HashSet<string> getDirectRoles()
        {
            return roles;
        }

        string getDirectRolesString()
        {
            return setToString(roles);
        }

        string getAllRolesString()
        {
            return setToString(getAllRoles());
        }

        public string setToString(HashSet<string> set)
        {

            // Should be sorted
            // Iterator it = (new java.util.TreeSet(roles)).iterator();

            StringBuilder sb = new StringBuilder();

            foreach (var s in set)
            {
                if (sb.Length > 0)
                {
                    sb.Append(',');
                }

                sb.Append(s);
            }

            return sb.ToString();
        }

        /**
         * Gets direct and nested roles.
         */
        public HashSet<string> getAllRoles()
        {

            HashSet<string> newSet = new HashSet<string>();

            addGranteeAndRoles(newSet);

            // Since we added "Grantee" _in addition to Roles, need to remove self.
            newSet.Remove(granteeName);

            return newSet;
        }

        /**
         * Adds to given Set this.sName plus all roles and nested roles.
         *
         * @return Given role with new elements added.
         */
        private HashSet<string> addGranteeAndRoles(HashSet<string> set)
        {     

            set.Add(granteeName);

            foreach (string candidateRole in roles)
            {
                if (!set.Contains(candidateRole))
                {
                    try
                    {
                        granteeManager.getRole(candidateRole).addGranteeAndRoles(
                            set);
                    }
                    catch (HsqlException he)
                    {
                        throw new Exception(he.Message);
                    }
                }
            }

            return set;
        }

        public bool hasRoleDirect(string role)
        {
            return roles.Contains(role);
        }

        public bool hasRole(string role)
        {
            return getAllRoles().Contains(role);
        }

        public string allRolesString()
        {

            HashSet<string> allRoles = getAllRoles();

            if (allRoles.Count < 1)
            {
                return null;
            }
           
            StringBuilder sb = new StringBuilder();

            foreach (var s in allRoles)
            {
                if (sb.Length > 0)
                {
                    sb.Append(',');
                }

                sb.Append(s);
            }

            return sb.ToString();
        }

        /**
         * Grants the specified rights on the specified database object. <p>
         *
         * Keys stored _in rightsMap for database tables are their HsqlName
         * attribute. This allows rights to persist when a table is renamed. <p>
         */
        public void grant(Object dbobject, int rights)
        {

            if (rights == 0)
            {
                return;
            }

            int n = rightsMap.ContainsKey(dbobject) ? rightsMap[dbobject] : 0;

            n |= rights;

            rightsMap[dbobject] = n;
        }

        /**
         * Revokes the specified rights on the specified database object. <p>
         *
         * If, after removing the specified rights, no rights remain on the
         * database object, then the key/value pair for that object is removed
         * from the rights map
         */
        public void revoke(Object dbobject, int rights)
        {

            if (rights == 0)
            {
                return;
            }

            int n = rightsMap.ContainsKey(dbobject) ? rightsMap[dbobject] : 0;

            if (n == 0)
            {
                return;
            }

            rights = n & (GrantConstants.ALL - rights);

            if (rights == 0)
            {
                rightsMap.Remove(dbobject);
            }
            else
            {
                rightsMap[dbobject] = rights;
            }
        }

        /**
         * Revokes all rights on the specified database object.<p>
         *
         * This method removes any existing mapping from the rights map
         */
        public void revokeDbObject(Object dbobject)
        {
            rightsMap.Remove(dbobject);
            fullRightsMap.Remove(dbobject);
        }

        /**
         * Revokes all rights from this Grantee object.  The map is cleared and
         * the database administrator role attribute is set false.
         */
        public void clearPrivileges()
        {

            roles.Clear();
            rightsMap.Clear();
            fullRightsMap.Clear();

            _isAdminDirect = false;
        }

        /**
         * Checks if any of the rights represented by the rights
         * argument have been granted on the specified database object. <p>
         *
         * This is done by checking that a mapping exists _in the rights map
         * from the dbobject argument for at least one of the rights
         * contained _in the rights argument. Otherwise, it throws.
         */
        public void check(HsqlNameManager.HsqlName dbobject, int rights)
        {

            if (!isAccessible(dbobject, rights))
            {
                throw Trace.error(Trace.ACCESS_IS_DENIED);
            }
        }

        public void check(string dbobject)
        {

            if (!isAccessible(dbobject))
            {
                throw Trace.error(Trace.ACCESS_IS_DENIED);
            }
        }

        /**
         * Returns true if any of the rights represented by the
         * rights argument has been granted on the database object identified
         * by the dbobject argument. <p>
         *
         * This is done by checking that a mapping exists _in the rights map
         * from the dbobject argument for at least one of the rights
         * contained _in the rights argument.
         *
         * Only does one level of recursion to check the PUBLIC role.
         */
        public bool isAccessible(HsqlNameManager.HsqlName dbObject, int rights)
        {

            if (_isAdmin)
            {
                return true;
            }

            if (pubGrantee != null && pubGrantee.isAccessible(dbObject, rights))
            {
                return true;
            }

            int n = fullRightsMap.ContainsKey(dbObject) ? fullRightsMap[dbObject] : 0; 

            if (n != 0)
            {
                return (n & rights) != 0;
            }

            return false;
        }

        /**
         * Returns true if any right at all has been granted to this User object
         * on the database object identified by the dbObject argument.
         */
        public bool isAccessible(string functionName)
        {

            if (functionName.StartsWith("org.hsqldb.Library")
                    || functionName.StartsWith("java.lang.Math"))
            {
                return true;
            }

            if (_isAdmin)
            {
                return true;
            }

            if (pubGrantee != null && pubGrantee.isAccessible(functionName))
            {
                return true;
            }

            int n = fullRightsMap.ContainsKey(functionName) ? rightsMap[functionName] : 0; 

            return n != 0;
        }

        /**
         * Returns true if any of the rights represented by the
         * rights argument has been granted on the database object identified
         * by the dbObject argument. <p>
         *
         * This is done by checking that a mapping exists _in the rights map
         * from the dbObject argument for at least one of the rights
         * contained _in the rights argument.
         *
         * Considers none of pubGranee, nested roles, admin privs, globally
         * available Class object.
         */
        public bool isDirectlyAccessible(Object dbObject,
                                               int rights)
        {

            int n = 0;

            try
            {
                n = rightsMap[dbObject];
            }
            catch (Exception) { }
           

            if (n != 0)
            {
                return (n & rights) != 0;
            }

            return false;
        }

        /**
         * Returns true if any right at all has been granted to this User object
         * on the database object identified by the dbObject argument.
         */
        public bool isAccessible(HsqlNameManager.HsqlName dbObject)
        {
            return isAccessible(dbObject, GrantConstants.ALL);
        }

        /**
         * Checks whether this Grantee has administrative privs either directly
         * or indirectly. Otherwise it throws.
         */
        public void checkAdmin()
        {

            if (!isAdmin())
            {
                throw Trace.error(Trace.ACCESS_IS_DENIED);
            }
        }

        /**
         * Returns true if this Grantee has administrative privs either directly
         * or indirectly.
         */
        public bool isAdmin()
        {
            return _isAdmin;
        }

        /**
         * Returns true if this grantee object is for a user with Direct
         * database administrator privileges.
         * I.e., if this User/Role has Admin priv. directly, not via a
         * nested Role.
         */
        bool isAdminDirect()
        {
            return _isAdminDirect;
        }

        /**
         * Retrieves the distinct set of Java <code>Class</code> FQNs
         * for which this <code>User</code> object has been
         * granted <code>ALL</code> (the Class execution privilege). <p>
         * @param andToPublic if <code>true</code>, then the set includes the
         *        names of classes accessible to this <code>User</code> object
         *        through grants to its Roles + <code>PUBLIC</code>
         *        <code>User</code> object attribute, else only role grants
         *        + direct grants are included.
         * @return the distinct set of Java Class FQNs for which this
         *        this <code>User</code> object has been granted
         *        <code>ALL</code>.
         */
        public HashSet<string> getGrantedClassNames(bool andToPublic)
        {

            Dictionary<object, int> rights;        
            int right;          

            rights = rightsMap;

            HashSet<string> _out = getGrantedClassNamesDirect();

            if (andToPublic && pubGrantee != null)
            {
                rights = pubGrantee.rightsMap;     
         
                foreach (var key in rights.Keys)
                {                 

                    if (key is string)
                    {
                        right = 0;
                        try
                        {
                            right = rights[key];
                        }
                        catch (Exception) { }

                        if (right == GrantConstants.ALL)
                        {
                            _out.Add((string)key);
                        }
                    }
                }
            }
       

            foreach (var role in getAllRoles())
            {
                _out.UnionWith(
                    ((Grantee)granteeManager.getRole(
                        (String)role)).getGrantedClassNamesDirect());
            }

            return _out;
        }

        /**
         * Retrieves the distinct set of Java <code>Class</code> FQNs
         * for which this <code>User</code> object has directly been
         * granted <code>ALL</code> (the Class execution privilege).
         *
         * Does NOT check nested the pubGrantee nor nested roles.
         * @return the distinct set of Java Class FQNs for which this
         *        this <code>User</code> object has been granted
         *        <code>ALL</code>.
         *
         */
        HashSet<string> getGrantedClassNamesDirect()
        {

            Dictionary<object, int> rights;
            HashSet<string> _out;          
            int right =0;

            rights = rightsMap;
            _out = new HashSet<string>();          

            foreach (var key in rightsMap.Keys)
            {       

                if (key is String)
                {
                    right = 0;
                    try
                    {
                        right = rights[key];
                    }
                    catch (Exception) { }

                    if (right == GrantConstants.ALL)
                    {
                        _out.Add((string)key);
                    }
                }
            }

            return _out;
        }

        /**
         * Retrieves a string[] whose elements are the names of the rights
         * explicitly granted with the GRANT command to this <code>User</code>
         * object on the <code>Table</code> object identified by the
         * <code>name</code> argument.
         * * @return array of Strings naming the rights granted to this
         *        <code>User</code> object on the <code>Table</code> object
         *        identified by the <code>name</code> argument.
         * @param name a <code>Table</code> object identifier
         *
         */
        public String[] listGrantedTablePrivileges(HsqlNameManager.HsqlName name)
        {
            int right =0;
            try
            {
                right = rightsMap[name];
            }
            catch(Exception){}

            return GranteeManager.getRightsArray(right);
        }

        /**
         * Violates naming convention (for backward compatibility).
         * Should be "setAdminDirect(bool").
         */
        public void setAdminDirect()
        {
            _isAdmin = _isAdminDirect = true;
        }

        /**
         * Recursive method used with ROLE Grantee objects to set the fullRightsMap
         * and admin flag for all the roles.
         *
         * If a new ROLE is granted to a ROLE Grantee object, the ROLE should first
         * be added to the Set of ROLE Grantee objects (roles) for the grantee.
         * The grantee will be the parameter.
         *
         * If the direct permissions granted to an existing ROLE Grentee is
         * modified no extra initial action is necessary.
         * The existing Grantee will b the parameter.
         *
         * If an existing ROLE is REVOKEed from a ROLE, it should first be removed
         * from the set of ROLE Grantee objects _in the containing ROLE.
         * The containing ROLE will be the parameter.
         *
         * If an existing ROLE is DROPped, all its privileges should be cleared
         * first. The ROLE will be the parameter. After calling this method on
         * all other roles, the DROPped role should be removed from all grantees.
         *
         * After the initial modification, this method should be called iteratively
         * on all the ROLE Grantee objects contained _in RoleManager.
         *
         * The updateAllRights() method is then called iteratively on all the
         * USER Grantee objects contained _in UserManager.
         * @param role a modified, revoked or dropped role.
         * @return true if this Grantee has possibly changed as a result
         */
        public bool updateNestedRoles(string role)
        {

            bool hasNested = false;
            bool isSelf = role.Equals(granteeName);

            if (!isSelf)
            {              

                foreach (var roleName in roles)
                {           

                    try
                    {
                        Grantee currentRole = granteeManager.getRole(roleName);

                        hasNested |= currentRole.updateNestedRoles(role);
                    }
                    catch (HsqlException) { }
                }
            }

            if (hasNested)
            {
                updateAllRights();
            }

            return hasNested || isSelf;
        }

        /**
         * Method used with all Grantee objects to set the full set of rights
         * according to those inherited form ROLE Grantee objects and those
         * granted to the object itself.
         */
        public void updateAllRights()
        {

            fullRightsMap.Clear();

            _isAdmin = _isAdminDirect;    

            foreach (var roleName in roles)
            {              

                try
                {
                    Grantee currentRole = granteeManager.getRole(roleName);

                    foreach(var tk in currentRole.fullRightsMap.Keys){
                        fullRightsMap[tk] = currentRole.fullRightsMap[tk];
                    }                    

                    _isAdmin |= currentRole.isAdmin();
                }
                catch (HsqlException ) { }
            }

         
           foreach(var tk in rightsMap.Keys){
                    fullRightsMap[tk] = rightsMap[tk];
          }                    
        }
    }

}
